﻿using System;
using System.Data;
using System.Text;
using System.Collections;

namespace BizElements.Core
{
    #region Documentation.
    /// <summary>
    /// Specifies the sort for the result set of a <see cref="SelectStatement"/>.
    /// </summary>
    /// <remarks><para>Contains a list of order by expressions. An order by expressions specifies a column on which 
    /// to sort and the sort direction. Ascending (ASC) direction sorts from the lowest value to highest value. 
    /// Descending direction (DESC) sorts from the highest value to lowest value. Null values are treated 
    /// as the lowest possible values.</para>
    /// <para>OrderByClause may be also used to create a sort expression for a <see cref="DataView"/> 
    /// (see <see cref="GetDataViewSortExpression"/>).</para>
    /// <para><b>Restrictions.</b> Some RDBMSs don't support ORDER BY for all field types. Eg. the text, 
    /// ntext, or image data type cannot be used in an ORDER BY clause on SQL Server 7 and 2000.</para></remarks>
    /// <example>
    /// The following example creates an OrderByClause and uses it to render a DataView sort expression.
    /// <code>
    /// public string GetEmployeesSorter()
    /// {
    /// 	// Returns sort expression: "LastName ASC, FirstName ASC, BirthDate DESC"
    /// 
    /// 	EmployeesMeta employees = new EmployeesMeta();			
    /// 	OrderByClause orderBy = new OrderByClause();
    /// 	orderBy.Add(employees.LastName, true);
    /// 	orderBy.Add(employees.FirstName, true);
    /// 	orderBy.Add(employees.BirthDate, false);
    /// 	
    /// 	return orderBy.GetDataViewSortExpression();
    /// }
    /// </code>
    /// </example>
    #endregion
    [Serializable]
    public sealed class OrderByClause : IRenderSql
    {
        #region Members.

        // List 'columns' contains IDbColumn and/or string objects.
        ArrayList columns = new ArrayList();

        // Contains string objects: "ASC" and "DESC".
        ArrayList directions = new ArrayList();

        #endregion

        #region Properties.

        /// <summary>
        /// Gets the number of sort expressions in the ORDER BY clause.
        /// </summary>
        public int Count
        {
            get { return this.columns.Count; }
        }

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the OrderByClause class.
        /// </summary>
        /// <remarks>Creates an empty ORDER BY clause.</remarks>
        public OrderByClause()
        {
        }

        /// <summary>
        /// Initializes a new instance of the OrderByClause class.
        /// </summary>
        /// <param name="firstField">First sort column in the clause.</param>
        /// <remarks>Creates an ORDER BY clause and sets the first sort expression.</remarks>
        public OrderByClause(IDbColumn firstField)
        {
            Add(firstField);
        }

        /// <summary>
        /// Initializes a new instance of the OrderByClause class.
        /// </summary>
        /// <param name="firstField">First sort column in the clause.</param>
        /// <param name="ascending">Specifies whether the values in the specified column should be sorted 
        /// in ascending order, from lowest value to highest value.</param>
        /// <remarks>Creates an ORDER BY clause and sets the first sort expression.</remarks>
        public OrderByClause(IDbColumn firstField, bool ascending)
        {
            Add(firstField, ascending);
        }

        /// <summary>
        /// Initializes a new instance of the OrderByClause class.
        /// </summary>
        /// <param name="firstField">Name of the first sort column in the clause.</param>
        /// <param name="ascending">Specifies whether the values in the specified column should be sorted 
        /// in ascending order, from lowest value to highest value.</param>
        /// <remarks>Creates an ORDER BY clause and sets the first sort expression.</remarks>
        public OrderByClause(string firstField, bool ascending)
        {
            Add(firstField, ascending);
        }

        #endregion

        #region Add, Clear.

        /// <summary>
        /// Adds new expression to the clause.
        /// </summary>
        /// <param name="field">Column whose values are to be sorted in ascending order. Null is not allowed.</param>
        public void Add(IDbColumn field)
        {
            if (field == null)
                throw new ArgumentNullException("field", Messages.OrderByClause_FieldMayNotBeNull);

            this.columns.Add(field);
            this.directions.Add("ASC");
        }

        /// <summary>
        /// Adds new expression to the clause.
        /// </summary>
        /// <param name="field">Column. Null is not allowed.</param>
        /// <param name="ascending">Specifies whether the values in the specified column should be sorted 
        /// in ascending order, from lowest value to highest value.</param>
        public void Add(IDbColumn field, bool ascending)
        {
            if (field == null)
                throw new ArgumentNullException("field", Messages.OrderByClause_FieldMayNotBeNull);

            this.columns.Add(field);
            if (ascending)
                this.directions.Add("ASC");
            else
                this.directions.Add("DESC");
        }

        /// <summary>
        /// Adds new expression to the clause.
        /// </summary>
        /// <param name="columnName">Column name. Null or empty string is not allowed.</param>
        /// <param name="ascending">Specifies whether the values in the specified column should be sorted 
        /// in ascending order, from lowest value to highest value.</param>
        public void Add(string columnName, bool ascending)
        {
            if (string.IsNullOrEmpty(columnName))
                throw new ArgumentException(Messages.OrderByClause_ColumnNameMayNotBeNullOrEmpty, "columnName");

            this.columns.Add(columnName);
            if (ascending)
                this.directions.Add("ASC");
            else
                this.directions.Add("DESC");
        }

        /// <summary>
        /// Removes all sort expressions from the collection.
        /// </summary>
        public void Clear()
        {
            this.columns.Clear();
            this.directions.Clear();
        }

        #endregion

        #region GetSortExpression.

        /// <summary>
        /// Gets sort expression at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the expression to get.</param>
        /// <returns>Expression at the specified index.</returns>
        public SortExpression GetSortExpressionAt(int index)
        {
            IDbColumn field = this.columns[index] as IDbColumn;
            string columnName = (field == null) ? this.columns[index] as string : null;
            string dir = (string)this.directions[index];
            bool ascending = (dir == "ASC");

            SortExpression expression = new SortExpression(field, columnName, ascending);
            return expression;
        }

        /// <summary>
        /// Gets all sort expressions.
        /// </summary>
        /// <returns>Array of sort expressions.</returns>
        public SortExpression[] GetAllSortExpressions()
        {
            SortExpression[] allExpressions = new SortExpression[this.Count];
            for (int idxExpression = 0; idxExpression < allExpressions.Length; idxExpression++)
                allExpressions[idxExpression] = GetSortExpressionAt(idxExpression);

            return allExpressions;
        }

        #endregion

        #region RenderUpdate.

        /// <summary>
        /// Renders ORDER BY clause.
        /// </summary>
        /// <param name="dbms">Target DBMS.</param>
        /// <param name="output">StringBuilder to which SQL is appended.</param>
        /// <param name="parameters">SQL parameter collection to which the object's and its children's
        /// parameters are added. After the rendering is done the collection contains all parameters with unique names.</param>
        public void Render(DbmsType dbms, StringBuilder output, DbParameterCollection parameters)
        {
            if (this.columns.Count > 0)
                output.Append("ORDER BY ");

            for (int colIdx = 0; colIdx < this.columns.Count; colIdx++)
            {
                if (this.columns[colIdx] is IDbColumn)
                    ((IDbColumn)this.columns[colIdx]).RenderFullName(dbms, output);
                else if (this.columns[colIdx] is String)
                    output.Append((string)this.columns[colIdx]);

                output.Append(" ");
                output.Append((string)this.directions[colIdx]);

                if (colIdx < this.columns.Count - 1)
                    output.Append(", ");
            }
        }

        /// <summary>
        /// Creates a DataView sort expression.
        /// </summary>
        /// <returns>DataView sort expression. Empty string if the clause is empty.</returns>
        public string GetDataViewSortExpression()
        {
            if (this.Count == 0)
                return "";

            StringBuilder output = new StringBuilder();
            for (int colIdx = 0; colIdx < this.columns.Count; colIdx++)
            {
                if (this.columns[colIdx] is IDbColumn)
                {
                    IDbColumn field = (IDbColumn)this.columns[colIdx];
                    output.Append(field.Alias);
                }
                else if (this.columns[colIdx] is String)
                {
                    string field = (string)this.columns[colIdx];
                    output.Append(field);
                }

                output.Append(" ");
                output.Append((string)this.directions[colIdx]);

                if (colIdx < this.columns.Count - 1)
                    output.Append(", ");
            }

            return output.ToString();
        }

        /// <summary>
        /// Returns the SQL code for this object.
        /// </summary>
        /// <returns>ORDER BY clause.</returns>
        public override string ToString()
        {
            return ToString(DbmsType.SqlServer_2000);
        }

        /// <summary>
        /// Returns the SQL code for this object.
        /// </summary>
        /// <param name="dbms">Target DBMS.</param>
        /// <returns>ORDER BY clause.</returns>
        public string ToString(DbmsType dbms)
        {
            StringBuilder orderBy = new StringBuilder();
            DbParameterCollection parameters = new DbParameterCollection();
            Render(dbms, orderBy, parameters);
            return orderBy.ToString();
        }

        #endregion

        #region IsNullOrEmpty.

        /// <summary>
        /// Indicates whether the specified order by clause is empty or a <n>null</n> reference.
        /// </summary>
        /// <param name="value">A <see cref="OrderByClause"/> reference.</param>
        /// <returns><b>true</b> if the value parameter is empty or a <n>null</n> reference; otherwise, <b>false</b>.</returns>
        public static bool IsNullOrEmpty(OrderByClause value)
        {
            bool isNullOrEmpty = (value == null) || (value.Count == 0);
            return isNullOrEmpty;
        }

        #endregion
    }

    /// <summary>
    /// Contains sort expression info.
    /// </summary>
    public sealed class SortExpression
    {
        #region Internal CTor.

        internal SortExpression(IDbColumn field, string columnName, bool ascending)
        {
            this.Field = field;
            this.ColumnName = columnName;
            this.Ascending = ascending;
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets the column whose values are sorted.
        /// </summary>
        /// <value><see cref="IDbColumn"/> object or <b>null</b> if <see cref="ColumnName"/> property is set.</value>
        public IDbColumn Field { get; private set; }

        /// <summary>
        /// Gets the name of the column whose values are sorted.
        /// </summary>
        /// <value>Column name or <b>null</b> if <see cref="Field"/> property is set.</value>
        public string ColumnName { get; private set; }

        /// <summary>
        /// Gets whether the values in the specified column should be sorted in ascending order, from lowest value to highest value.
        /// </summary>
        /// <value><b>true</b> if ascending order is used; <b>false</b> if descending order is used. Default is <b>true</b>.</value>
        public bool Ascending { get; private set; }

        #endregion
    }
}